'''
Created on Dec 24, 2011

@author: u
'''

import socket
from network import IndexConnection, JSONConnection
import sys
import asyncore
import weakref
import world
import logging

class ClientIndexConnection(IndexConnection):
    """
    Connects to the index server
    """
    
    def __init__(self, companion, index_response):
        IndexConnection.__init__(self)
        
        self._commands["index_response"] = index_response
        
        message = {"cmd" : "client_hello"}
        companion.index_hello_hook(message)
        self.write_json(message)
        
    def handle_connect(self):
        logging.info("Connected to index")

    def handle_close(self):
        self.close()
        if not self.response_received:
            logging.error("Index server connection closed without a response")
            sys.exit(-1)

class BlockConnection(JSONConnection, asyncore.dispatcher):
    def __init__(self, host, port, client_base):
        JSONConnection.__init__(self)
        asyncore.dispatcher.__init__(self)

        self._commands["aura_peers"] = client_base.aura_peers
        
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))
        
        message = {"cmd" : "client_hello"}
        message["id"] = client_base.companion.client_id
        message["port"] = client_base.aura_server.local_port
        client_base.companion.block_hello_hook(message)
        self.write_json(message)   

    def handle_connect(self):
        logging.debug("Connected to block server")

    def handle_close(self):
        self.close()
        logging.info("Connection to block server closed")

        
class AuraConnection(JSONConnection, asyncore.dispatcher):
    def __init__(self, companion, host, port):
        JSONConnection.__init__(self)
        asyncore.dispatcher.__init__(self)
        
        self.companion = companion
        
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))
        
        message = {"cmd" : "client_hello"}
        message["id"] = companion.client_id
        self.companion.aura_hello_hook(message)
        self.write_json(message)

    def handle_connect(self):
        logging.debug("Connected to client with aura")

    def handle_close(self):
        logging.debug("Aura connection closed")
        self.close()
        self.companion.client_bye(self.companion.client_id)
        
class AuraHandler(JSONConnection, asyncore.dispatcher):
    def __init__(self, sock, companion):
        JSONConnection.__init__(self)
        asyncore.dispatcher.__init__(self, sock)
        
        def hello_wrapper(message):
            self.client_id = message["id"]
            companion.client_hello(message)  
        
        self._commands.update({"client_hello" : hello_wrapper,
                          "client_info" : companion.client_info,
                          "client_message" : self.client_message})
        
        self.client_bye = companion.client_bye
        self.companion = companion
        
    def client_message(self, message):
        if self.companion.inside_focus(message["pos"]):
            print "Message venant d'un tableau : ", message["message"]
    
    def handle_close(self):
        self.close()
        if hasattr(self, "client_id"):
            logging.info("Client %s disconnected", self.client_id)
            
            self.client_bye(self.client_id)
        
class AuraServer(asyncore.dispatcher):
    def __init__(self, companion, host='0.0.0.0'):
        asyncore.dispatcher.__init__(self)
        
        self.companion = companion

        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, 0))
        self.listen(16)
    
        self.connections = weakref.WeakSet()
        
        _, self.local_port = self.socket.getsockname()

    def handle_accept(self):
        pair = self.accept()
        if pair:
            sock, addr = pair
            logging.debug("Incoming connection from %s", repr(addr))
            self.connections.add(AuraHandler(sock, self.companion))
        
class ClientBase:
    def __init__(self, companion):  
        #companion class is for example the Context class
        #which must contain
        #client_id : an attr
        #load : called to laod the 3d scene
        #client_hello : function called when an other client announce it self
        #client_update : when we receive a position update from another client
        #client_bye : wall when the client disconnected
        #
        #called when sending hello message :
        #index_hello_hook
        #block_hello_hook
        #aura_hello_hook
        
              
        self.companion = companion

        self.aura_connections = weakref.WeakValueDictionary()
        
    def init_network(self, client_type):
        logging.info("Connecting to index server")
        self.block_connections = weakref.WeakValueDictionary()
        self.index_connection = ClientIndexConnection(self.companion, self.index_response)
        self.aura_server = AuraServer(self.companion)
        
    def index_response(self, message):
        self.companion.client_id = message["id"]
        self.blocks = world.unformat_blocks(message["blocks"])
        
        self.companion.load_scene(serv_to_client(message["start"]))
        
        self.current_block = (-1, -1)
        self.update_current_block()
        self.update_block_connections()
        
    def update_current_block(self):
        position = self.companion.get_position()
        block_coord = world.world_to_block(client_to_serv(position))
        
        if block_coord != self.current_block:
            self.current_block = block_coord
            self.neighbour_blocks = world.neighbours(self.blocks, self.current_block)
            self.neighbour_coords = world.neighbour_coords(self.neighbour_blocks)

    def update_block_connections(self):
        #closing connections that are not needed anymore
        for block_coord, conn in self.block_connections.items():
            if block_coord not in [self.current_block] + self.neighbour_coords:
                logging.debug("Disconnecting from block server %s", block_coord)
                conn.close()
        
        for block_coord, remote_host in [(self.current_block, self.blocks[self.current_block])] + self.neighbour_blocks:
            if block_coord not in self.block_connections:
                host, port = remote_host
                logging.debug("Connecting to block server %s, %s:%s", block_coord, host, port)
                self.block_connections[block_coord] = BlockConnection(host, port, self)

    def send_update(self, block_message, aura_message):
        self.update_current_block()
        self.update_block_connections()
        
        for block_connection in self.block_connections.values():
            block_connection.write_json(block_message)

        for client in self.aura_connections.values():
            client.write_json(aura_message)
            
    def poll(self):
        asyncore.poll2()
        
    def aura_peers(self, message):
        client_list = message["peers"]
        
        client_ids = set()
        for client_id, _ in client_list:
            client_ids.add(client_id)
            
        for client_id, conn in self.aura_connections.items():
            if client_id not in client_ids:
                logging.debug("Aura off : %s -> %s", self.companion.client_id, client_id)
                conn.close()
                
        for client_id, remote_host in client_list:
            if not client_id in self.aura_connections:
                host, port = remote_host
                logging.debug("Aura on : %s -> %s %s:%s", self.companion.client_id, client_id, host, port)
                self.aura_connections[client_id] = AuraConnection(self.companion, host, port)

    def send_message(self, client_id, position, message_txt):
        message = {"cmd" : "client_message"}
        message["id"] = self.companion.client_id
        message["message"] = message_txt
        message["pos"] = position
        
        try:
            self.aura_connections[client_id] .write_json(message)
        except KeyError:
            pass

def client_to_serv(position):
    x, _, z = position
    
    return z, x

def serv_to_client(position):
    x, y = position
    
    return y, world.default_height, x